from kinyx.logging_config import get_logger
import os
logger = get_logger(os.path.basename(__file__))

import inspect

__DEFAULT_FPS__ = 25

import subprocess
import re

from kinyx.command_util import input_str
from kinyx.command_util import input_int

def __image_file_pattern(image_directory_path, image_file_ext):
    return os.path.join(image_directory_path, '%05d' + '.' + image_file_ext)

def extract(
    input_video_file_path       :str    = None,
    fps                         :int    = 0,
    output_image_directory_path :str    = None,
    output_image_file_ext       :str    = 'png',
):
    logger.debug(f'running [{inspect.currentframe().f_code.co_name}] ...')
    
    if input_video_file_path is None:
        input_video_file_path = input_str(
            input_info      = 'path',
            help_message    = 'input video file path',
            endswith        = '.mp4',
        )
    if not isinstance(input_video_file_path, str):
        raise ValueError('parameter input_video_file_path must be a string value.')

    if output_image_directory_path is None:
        output_image_directory_path = input_str(
            input_info      = 'path',
            help_message    = 'output image directory path',
        )
    if not isinstance(output_image_directory_path, str):
        raise ValueError('parameter output_image_directory_path must be a string value.')

    if output_image_file_ext is None:
        output_image_file_ext = input_str(
            input_info      = 'ext',
            help_message    = 'output image file ext',
            optionals       = ['png', 'jpg'],
            default_value   = 'png',
        )
    if not isinstance(output_image_file_ext, str):
        raise ValueError('parameter output_image_file_ext must be a string value.')

    if fps is None:
        fps = input_int(
            input_info      = 'fps',
            range_min       = 0,
            range_max       = 240,
            default_value   = 0,
        )
    if fps == 0:
        logger.debug('fps is 0 meas use default fps')
        logger.debug('try to read fps from video information ...')
        try:
            ffmpeg_command = FFMPEG_CMD(input_video_file_path, '-')
            ffmpeg_command.i('f', 'null')
            run_result = subprocess.run(ffmpeg_command.cmd(), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
            if run_result.returncode == 0:
                logger.debug('load video success, read fps from video information ...')
                console_output_text = run_result.stderr
                if console_output_text is None or console_output_text == '':
                    console_output_text = run_result.stdout
                if console_output_text is None or console_output_text == '':
                    fps = __DEFAULT_FPS__
                    logger.warning(f'can not read fps from video information, cause it is blank, use default fps={fps}')
                else:
                    match = re.search(r'(\d+) fps', console_output_text)
                    if match:
                        fps = match.group(1)
                        logger.info(f'read fps={fps} from video information')
                    else:
                        fps = __DEFAULT_FPS__
                        logger.warning(f'can not read fps from video information, cause can not match it, use default fps={fps}')
            else:
                fps = __DEFAULT_FPS__
                logger.warning(f'can not read fps from video information, cause command return code is not 0, use default fps={fps}')
        except:
            fps = __DEFAULT_FPS__
            logger.warning(f'can not read fps from video information, cause command excute throws some error, use default fps={fps}')
    
    ffmpeg_command = FFMPEG_CMD(input_video_file_path, __image_file_pattern(output_image_directory_path, output_image_file_ext))
    ffmpeg_command.i('r', fps)              # 帧率
    subprocess.run(ffmpeg_command.cmd(), shell=True)

def merge(
    input_image_directory_path :str     = None,
    input_image_file_ext       :str     = 'png',
    output_video_file_path     :str     = None,
    fps                        :int     = 0,
):
    logger.debug(f'running [{inspect.currentframe().f_code.co_name}] ...')
    
    if input_image_directory_path is None:
        input_image_directory_path = input_str(
            input_info      = 'path',
            help_message    = 'input image directory path',
        )
    if not isinstance(input_image_directory_path, str):
        raise ValueError('parameter input_image_directory_path must be a string value.')

    if input_image_file_ext is None:
        input_image_file_ext = input_str(
            input_info      = 'ext',
            help_message    = 'input image file ext',
            optionals       = ['png', 'jpg'],
            default_value   = 'png',
        )
    if not isinstance(input_image_file_ext, str):
        raise ValueError('parameter input_image_file_ext must be a string value.')

    if output_video_file_path is None:
        output_video_file_path = input_str(
            input_info      = 'path',
            help_message    = 'output video file path',
            endswith        = '.mp4',
        )
    if not isinstance(output_video_file_path, str):
        raise ValueError('parameter output_video_file_path must be a string value.')
    
    if fps is None:
        fps = input_int(
            input_info      = 'fps',
            range_min       = 0,
            range_max       = 240,
            default_value   = 0,
        )
    if not isinstance(fps, int):
        raise ValueError('parameter fps must be a int value.')
    if fps == 0:
        fps = __DEFAULT_FPS__
        logger.debug(f'fps is 0 meas use default fps={fps}')
    
    ffmpeg_command = FFMPEG_CMD(__image_file_pattern(input_image_directory_path, input_image_file_ext), output_video_file_path)
    ffmpeg_command.i('r', fps)              # 帧率
    ffmpeg_command.i('c:v', 'libx264rgb')   # 指定编码器
    ffmpeg_command.i('crf', 0)              # 视频质量（0: 无损）
    ffmpeg_command.i('pix_fmt', 'yuv444p')  # 像素格式
    ffmpeg_command.o('y', None)             # 强制替换目标文件
    subprocess.run(ffmpeg_command.cmd(), shell=True)

def join_option(key, value):
    result = ''
    if not key is None: result += ' ' + '-' + str(key)
    if not value is None: result += ' ' + str(value)
    return result

def join_options(options):
    result = ''
    for key, value in options.items():
        result += join_option(key, value)
    return result

class FFMPEG_CMD:
    def __init__(self, i:str, o:str):
        self.__exe = 'ffmpeg'
        self._i = i
        self._o = o
        self._input_options  = {}
        self._output_options = {}

    def i(self, key:str, value:(str, int, float)):
        self._input_options[key] = value
        return self

    def o(self, key:str, value:(str, int, float)):
        self._output_options[key] = value
        return self

    def cmd(self):
        result = self.__exe
        result += join_option('i', self._i)
        result += join_options(self._input_options)
        result += join_option(None, self._o)
        result += join_options(self._output_options)
        logger.info(f'exeute cmd -> {result}')
        return result